package com.kubepartners.impactmodel.engine;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.jgrapht.DirectedGraph;
import org.jgrapht.alg.CycleDetector;

import com.kubepartners.impactmodel.model.Edge;
import com.kubepartners.impactmodel.model.Graph;
import com.kubepartners.impactmodel.model.Node;

public class EngineImpl implements Engine {
	private Graph graph = null;
	private boolean error_state =false;
	private double epsilon=0;
	
	public EngineImpl(Graph graph) {
		this.graph = graph;
	}

	public Graph processGraph(Map<String, Double> map) {
		
		if(!isConsistent(this.graph)){
			System.out.println("Inconsistent graph!");
		}
		else{
			mapToGraph(map);
			calculate();
		}
		return graph;
	}
	
	public void calculate(){	
		Node root= graph.getRoot();
		System.out.println("I'm in the root "+root.getId()+", I'm setting up the INDUCED Capacity of the root");
		setInduced(root);
		
		//if the root is also a leaf than we need to set also the delivered capacity
		if (graph.isLeaf(root)){
			setDelivered(root);
		}
	}
	
	public Boolean isConsistent(Graph graph){

		//At the beginning the graph state is consistent
		this.error_state=false;
		
		/* 
		 * The following are all the consistency checks on the graph
		 */
		DirectedGraph<Node, Edge> new_g =(DirectedGraph<Node, Edge>)graph;

		//check if there are no cycles in the graph
		CycleDetector<Node,Edge> c_d = new CycleDetector<Node, Edge>(new_g);
		if(c_d.detectCycles()){
/*			Set<Node> cycle =c_d.findCycles();
			Iterator<Node> c = cycle.iterator();*/
			graph.setCyclic(true);
			this.error_state=true;
/*			System.out.println("A cycle in the graph has been detected");
			while(c.hasNext()){
				c.next().setInCycle(true);
			}*/
		}
	
		
		/* 
		 * The following are all the consistency checks on the nodes of the graph
		 */
		HashSet<Node> roots=new HashSet<Node>();
		Set<Node> nodes = graph.vertexSet();
		Iterator<Node> ns = nodes.iterator();
		while(ns.hasNext()){
			Node node = ns.next();
			//check that all the capacities are less than or equal to one
			if((node.getVertexCapacity()<0)||(node.getVertexCapacity()>1)){
				node.setVertexCapacityError(true);
				this.error_state=true;
				System.out.println("The vertex capacity of node "+node.getId()+" isn't in the range of [0,1]");
			}
			
			//check if the sum of the weights of the links incoming in each node is equal to one
			Set<Edge> edge_list=graph.incomingEdgesOf(node);
			Iterator<Edge> e_l=edge_list.iterator();
			double weight=0;
			while(e_l.hasNext()){
				Edge edge=(Edge)e_l.next();
				weight=weight+edge.getWeight();
				//System.out.println("edge: "+edge.getId()+" weight: "+edge.getWeight()+" weight: "+weight);
			}
			if((Math.abs(Graph.round(weight)-1)>epsilon)&&(!graph.isLeaf(node))){
				node.setIncomingLinksError(true);
				this.error_state=true;
				System.out.println("The sum of the weights of the incoming links in node "+node.getId()+" isn't equal to 1 (total weight= "+weight+")");
			}
			
			Set<Edge> edge_list_o=graph.outgoingEdgesOf(node);
			//Check whether the node is an orphan and has no children
			if((edge_list.size()==0)&&(edge_list_o.size()==0)){
				node.setUnconnectedError(true);
				this.error_state=true;
				System.out.println("The node "+node.getId()+" has no incoming neither ougoing links");
			}
			
			//Check whether the outgoing links are not zero for nodes that are not a root
			if(edge_list_o.size()==0){
				roots.add(node);
				if (roots.size()>1){
					Iterator<Node> rs = roots.iterator();
					System.out.println("Found one additional root with ID: "+node.getId());
					node.setRootError(true);
					while(rs.hasNext()){
						rs.next().setRootError(true);
					}
					this.error_state=true;
					System.out.println("The node "+node.getId()+" has no outgoing links but it is not a root (only one root per graph admitted!)");
				}
			}
			
			//no multiple nodes with the same label
			Iterator<Node>other_ns=nodes.iterator();
			while(other_ns.hasNext()){
				Node other_node=other_ns.next();
				if(node.getExternalID().compareTo(other_node.getExternalID())==0){
					if(node.getId()!=other_node.getId()){
						node.setAlias(true);
						other_node.setAlias(true);
						this.error_state=true;
						System.out.println("The (external) Id "+node.getExternalID()+" has been already used for another node in this graph");
					}
				}
			}
		}
		
		/* 
		 * The following are all the consistency checks on the links of the graph
		 */
		Set<Edge> edges = graph.edgeSet();
		Iterator<Edge> es = edges.iterator();
		
		while(es.hasNext()){
			Edge edge = es.next();
			
			//check that all the thresholds are less than or equal to one and bigger than 0
			if((edge.getThreshold()<0)||(edge.getThreshold()>1)){
				edge.setThreshold_error(true);
				this.error_state=true;
				System.out.println("The threshold of the link "+edge.getId()+" is not in the range of [0,1]");
			}
			
			//check if the weight of the link is less than or equal to one and bigger than 0
			if((edge.getWeight()<0)||(edge.getWeight()>1)){
				edge.setWeight_error(true);
				this.error_state=true;
				System.out.println("The weight of the link "+edge.getId()+" is not in the range of [0,1]");
			}
			
			//no edges with the same id
			Set<Edge> other_edges = graph.edgeSet();
			Iterator<Edge> other_es = other_edges.iterator();
			while(other_es.hasNext()){
				Edge other_edge=other_es.next();
				if(edge.getId().compareTo(other_edge.getId())==0){
					if(!(edge==other_edge)){
						edge.setAlias(true);
						this.error_state=true;
						System.out.println("The Id "+edge.getId()+" has been already used for another link in this graph");
					}
				}
			}
		}
		
		/* 
		 * The following are all the consistency checks on state of the graph
		 */
		
		if(graph.getGraph_state()=="structure"){
			//check that all the vertex capacities have a value and this value is the default one
			//check that no results are inserted and that all the induced and delivered capacities have their default value
			
		}
		else if(graph.getGraph_state()=="measured"){
			//check that all the vertex capacities have a value
			//check that no results are inserted and that all the induced and delivered capacities have their default value
			//check that only measurable nodes can have vertex capacities different from default
		}
		else if(graph.getGraph_state()=="results"){
			//check that results are in all the induced and delivered capacities of the nodes of the graph 
		}
		
		/*
		 * Send back result to the user
		 */
		return !this.error_state;
	}
	
	void mapToGraph(Map<String, Double> map){	
		Set<String> key_set = map.keySet();
		Iterator<String> ks = key_set.iterator();
		
		while(ks.hasNext()){
			String key= (String)ks.next();
		
			Node node=graph.findNodeById(key);
			if (node != null) {
				node.setVertexCapacity(map.get(key));
			}
			else {
				throw new RuntimeException("Key " + key + " not found in graph!");
			}
		}
	}
	
	void setInduced(Node node){
		
		if (graph.isLeaf(node)){
			if (!node.isInducedSet()) {
				node.setInducedCapacity(1.0);
				node.setInducedSet(true);
				System.out.println("\nnode "
						+ node.getId()
						+ " is a leaf, I'm setting INDUCED capacity to: "
						+ node.getInducedCapacity());
			}
		}
		else {
			if (!node.isInducedSet()) {
				//Retrieve incoming links    
				Set<Edge> incoming_links = graph.incomingEdgesOf(node);
				Iterator<Edge> i_l = incoming_links.iterator();

				boolean ind_set=false;
				double induced = 0;
				//for each incoming link it retrieves its weight and calls recursively the method getDelivered passing the start_node of the link
				while (i_l.hasNext()) {
					Edge link = (Edge) i_l.next();
					Node s_node = (Node) link.getStartNode();
					if ((link.getThreshold() >= 0) && (link.getThreshold() < 1)
							&& (getDelivered(s_node) <= link.getThreshold()) &&(!ind_set)) {
						induced = 0;
					System.out
								.println("The DELIVERED capacity of the node "
										+ s_node.getId()
										+ "( "
										+ s_node.getDeliveredCapacity()
										+ " )"
										+ " is less than the treshold "
										+ "( "
										+ link.getThreshold() + " )");
						ind_set=true;
					} else if (!ind_set) {
						induced = induced
								+ (getDelivered(s_node) * link.getWeight());
					} else {
						getDelivered(s_node);
					}
				}
				node.setInducedCapacity(induced);
				node.setInducedSet(true);
				System.out.println("INDUCED capacity of node "
						+ node.getId() + " has been set to: " + node.getInducedCapacity());
			}
			if (graph.getRoot().getId()==node.getId()){
				if(!node.isDeliveredSet()){
					setDelivered(node);
					System.out.println("node "
							+ node.getId()
							+ " is a Root, I'm setting INDUCED capacity to: "
							+ node.getInducedCapacity());
				}
			}
		}    
	}

	double getDelivered(Node node){
		//if the delivered capacity hasn't been yet set, we have to set it first
		if (!node.isDeliveredSet()){ 
			//System.out.println("The DELIVERED capacity of node "+node.getId()+" has not yet been set...");
			setDelivered(node);
			node.setDeliveredSet(true);
		    System.out.println("The DELIVERED capacity of node "+node.getId()+" set to: "+node.getDeliveredCapacity());
		}
		return node.getDeliveredCapacity();
	}

	void setDelivered(Node node){
		if (graph.isLeaf(node)){ 
		    if (!node.isDeliveredSet()) {
				node.setDeliveredCapacity(node.getVertexCapacity());
				node.setDeliveredSet(true);
				System.out.println("Node "
						+ node.getId() + " is a leaf, I'm setting DELIVERED capacity to: "
						+ node.getDeliveredCapacity());
			}
		    if (!node.isInducedSet()) {
				setInduced(node);
		    }
		}
		else {
			if (!node.isDeliveredSet()) {
				double delivered = node.getVertexCapacity() * getInduced(node);
				node.setDeliveredCapacity(Graph.round(delivered));
				node.setDeliveredSet(true);
				System.out
						.println("DELIVERED capacity of node "
								+ node.getId()
								+ " set to: "
								+ delivered);
			} else {
				System.out.println("Got here - nothing set!");
			}
		}
	}

	double getInduced(Node node){
	    //capacity has the value -1 only if it hasn't been yet set
	    if (!node.isInducedSet()){
	    	//System.out.println("The INDUCED capacity of node "+node.getId()+" has not yet been set...");
			setInduced(node);
			node.setInducedSet(true);
		    System.out.println("The INDUCED capacity of node "+node.getId()+" set to: "+node.getInducedCapacity());
	    }
	    return node.getInducedCapacity();
	}
}